import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test {

    //双指针

    //四数之和
    //给你一个由 n 个整数组成的数组 nums ，和一个目标值 target 。
    // 请你找出并返回满足下述全部条件且不重复的四元组
    // [nums[a], nums[b], nums[c], nums[d]]
    // （若两个四元组元素一一对应，则认为两个四元组重复）：
    //0 <= a, b, c, d < n
    //a、b、c 和 d 互不相同
    //nums[a] + nums[b] + nums[c] + nums[d] == target
    //你可以按 任意顺序 返回答案 。
    //示例 1：
    //输入：nums = [1,0,-1,0,-2,2], target = 0
    //输出：[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
    //示例 2：
    //输入：nums = [2,2,2,2,2], target = 8
    //输出：[[2,2,2,2]]
    //提示：
    //1 <= nums.length <= 200
    //-10^9 <= nums[i] <= 10^9
    //-10^9 <= target <= 10^9
    public List<List<Integer>> fourSum(int[] nums, int target){
        List<List<Integer>> ret = new ArrayList<>();
        // 1. 排序
        Arrays.sort(nums);

        int n = nums.length;
        for (int i = 0; i < n; ) {
            for (int j = i+1; j < n; ) {
                int left = j+1, right = n-1;
                long aim = (long)target - nums[i] - nums[j];
                while (left < right){
                    int sum = nums[left] + nums[right];
                    if (sum > aim) right--;
                    else if(sum < aim) left++;
                    else{
                        ret.add(Arrays.asList(nums[i],nums[j],nums[left++],nums[right--]));
                        while(left < right && nums[left] == nums[left-1]) left++;
                        while(left < right && nums[right] == nums[right+1]) right--;
                    }
                }
                j++;
                while(j<n && nums[j] == nums[j-1]) j++;
            }
            i++;
            while(i<n && nums[i] == nums[i-1]) i++;
        }
        return ret;
    }




    //三数之和
    //给你一个整数数组 nums ，判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足
    // i != j、i != k 且 j != k ，同时还满足 nums[i] + nums[j] + nums[k] == 0 。
    // 请你返回所有和为 0 且不重复的三元组。
    //注意：答案中不可以包含重复的三元组。
    //示例 1：
    //输入：nums = [-1,0,1,2,-1,-4]
    //输出：[[-1,-1,2],[-1,0,1]]
    //解释：
    //nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
    //nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
    //nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
    //不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
    //注意，输出的顺序和三元组的顺序并不重要。
    //示例 2：
    //输入：nums = [0,1,1]
    //输出：[]
    //解释：唯一可能的三元组和不为 0 。
    //示例 3：
    //输入：nums = [0,0,0]
    //输出：[[0,0,0]]
    //解释：唯一可能的三元组和为 0 。
    //提示：
    //3 <= nums.length <= 3000
    //-10^5 <= nums[i] <= 10^5
    public List<List<Integer>> threeSum(int[] nums){

        List<List<Integer>> ret = new ArrayList<>();

        Arrays.sort(nums);

        int n = nums.length;
        for (int i = 0; i < n; ) {
            if (nums[i] > 0) break;

            int left = i+1, right = n-1, target = -nums[i];
            while (left < right){
                int sum = nums[left] +nums[right];
                if (sum > target) right--;
                else if(sum < target) left++;
                else{
                    ret.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[left],nums[right])));
                    left++;
                    right--;
                    while(left < right && nums[left] == nums[left-1]) left++;
                    while (left < right && nums[right] == nums[right+1]) right--;
                }
            }
            i++;
            while(i<n && nums[i] == nums[i-1]) i++;
        }
        return ret;
    }


    //和为s的两个数字
    //购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是
    // target。若存在多种情况，返回任一结果即可。
    //示例 1：
    //输入：price = [3, 9, 12, 15], target = 18
    //输出：[3,15] 或者 [15,3]
    //示例 2：
    //输入：price = [8, 21, 27, 34, 52, 66], target = 61
    //输出：[27,34] 或者 [34,27]
    //提示：
    //1 <= price.length <= 10^5
    //1 <= price[i] <= 10^6
    //1 <= target <= 2*10^6
    public int[] twoSum(int[] nums, int target){
        int left = 0;
        int right = nums.length-1;
        while (left < right){
            int sum = nums[left] +nums[right];
            if (sum>target) right--;
            else if(sum<target) left++;
            else return new int[] {nums[left],nums[right]};
        }
        return new int[] {0};
    }



    //有效三角形的个数
    //给定一个包含非负整数的数组 nums ，返回其中可以组成三角形三条边的三元组个数
    //示例 1:
    //输入: nums = [2,2,3,4]
    //输出: 3
    //解释:有效的组合是:
    //2,3,4 (使用第一个 2)
    //2,3,4 (使用第二个 2)
    //2,2,3
    //示例 2:
    //输入: nums = [4,2,3,4]
    //输出: 4
    //提示:
    //1 <= nums.length <= 1000
    //0 <= nums[i] <= 1000
    public int triangleNumber(int[] nums){
        Arrays.sort(nums);

        int ret = 0, n = nums.length;
        for (int i = n-1; i >= 2; i--) {
            int left = 0, right = i-1;
            while(left < right){
                if (nums[left] +nums[right] >nums[i]){
                    ret += (right-left);
                    right--;
                }else{
                    left++;
                }
            }
        }
        return ret;
    }



    //盛水最多的容器
    //给定一个长度为 n 的整数数组 height 。有 n 条垂线，第 i 条线的两个端点是
    // (i, 0) 和 (i, height[i]) 。
    //找出其中的两条线，使得它们与 x 轴共同构成的容器可以容纳最多的水。
    //返回容器可以储存的最大水量。
    //说明：你不能倾斜容器。
    //示例 1：
    //输入：[1,8,6,2,5,4,8,3,7]
    //输出：49
    //解释：图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下，容器能够容纳水（表示为蓝色部分）的最大值为 49。
    //示例 2：
    //输入：height = [1,1]
    //输出：1
    //提示：
    //n == height.length
    //2 <= n <= 105
    //0 <= height[i] <= 104
    public int maxArea(int[] height) {
        int left = 0, right = height.length-1;
        int ret = 0;
        while (left < right){
            int v= Math.min(height[left],height[right]) *(right-left);
            ret = Math.max(ret,v);
            if (height[left]<height[right]) left++;
            else right--;
        }
        return ret;
    }



    //快乐数
    //编写一个算法来判断一个数 n 是不是快乐数。
    //「快乐数」 定义为：
    //对于一个正整数，每一次将该数替换为它每个位置上的数字的平方和。
    //然后重复这个过程直到这个数变为 1，也可能是 无限循环 但始终变不到 1。
    //如果这个过程 结果为 1，那么这个数就是快乐数。
    //如果 n 是 快乐数 就返回 true ；不是，则返回 false 。
    //示例 1：
    //输入：n = 19
    //输出：true
    //解释：
    //1^2 + 9^2 = 82
    //8^2 + 2^2 = 68
    //6^2 + 8^2 = 100
    //1^2 + 0^2 + 0^2 = 1
    //示例 2：
    //输入：n = 2
    //输出：false
    //提示：
    //1 <= n <= 2^31 - 1
    public int bitSum(int n){
        int sum = 0;
        while(n != 0){
            int t = n%10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }
    public boolean isHappy(int n) {
        int slow = n,fast = bitSum(n);
        while(slow != fast){
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
        return slow == 1;
    }


    //复写零
    //给你一个长度固定的整数数组 arr ，请你将该数组中出现的每个零都复写一遍
    // 并将其余的元素向右平移。
    //注意：请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改，
    // 不要从函数返回任何东西。
    //示例 1：
    //输入：arr = [1,0,2,3,0,4,5,0]
    //输出：[1,0,0,2,3,0,0,4]
    //解释：调用函数后，输入的数组将被修改为：[1,0,0,2,3,0,0,4]
    //示例 2：
    //输入：arr = [1,2,3]
    //输出：[1,2,3]
    //解释：调用函数后，输入的数组将被修改为：[1,2,3]
    public void duplicateZeros(int[] arr){
        int cur = 0,dest = -1 ,n = arr.length;
        while(cur<=n){
            if (arr[cur] == 0) dest+=2;
            else dest+=1;
            if(dest >=n-1) break;
            cur++;
        }

        if (dest == n){
            arr[n-1] = 0;
            cur--;
            dest-=2;
        }
        while(cur>=0){
            if(arr[cur] == 0){
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }else{
                arr[dest--] = arr[cur--];
            }
        }
    }



    //移动零
    //给定一个数组 nums，编写一个函数将所有 0 移动到数组的末尾，同时保持非零元素的相对顺序。
    //请注意 ，必须在不复制数组的情况下原地对数组进行操作。
    //示例 1:
    //输入: nums = [0,1,0,3,12]
    //输出: [1,3,12,0,0]
    //示例 2:
    //输入: nums = [0]
    //输出: [0]
    //提示:
    //1 <= nums.length <= 104
    //-231 <= nums[i] <= 231 - 1
    //
     public void moveZeroes(int[] nums) {
         for (int cur = 0,dest=-1; cur < nums.length; cur++) {
             if (nums[cur] != 0){
                 dest++;
                 int tmp = nums[cur];
                 nums[cur] = nums[dest];
                 nums[dest] = tmp;

             }
         }
    }
}
